package ratkit.parameter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;

import ratkit.parameter.RatKitParameter.Type;


/**
 * This class indicates that preperation of the {@code TestParameter} instances
 * that is required to define test scenario execution for the test method under
 * test. If the type of the parameter is defines as {@code Type#LIST} or
 * {@code Type#NUMBER} a list of parameter list according to the sweeping
 * instructions are prepared. Otherwise a list of {@code TestParameter} are
 * prepared.
 * 
 * @author İbrahim Çakırlar
 */
public class RatKitParameterSweeper {

	/**
	 * A list of sweeping parameters.
	 */
	private List<TestParameter> sweepingParameters = null;

	/**
	 * Initialize the test paremeter sweeper.
	 */
	public RatKitParameterSweeper() {
		this.sweepingParameters = new Vector<TestParameter>();
	}

	/**
	 * Returns a list of required test parameter for sweep runs
	 * 
	 * @param paramList
	 *            list of parameters test parameters.
	 * @return a list of of sweep run parameter lists with associate sweep run
	 *         values.
	 */
	public List<List<TestParameter>> sweepParameters(
			List<TestParameter> paramList) {
		List<List<TestParameter>> returnList = new Vector<List<TestParameter>>();
		// decide sweeping parameters first..
		for (TestParameter testParameter : paramList) {
			if (testParameter.getType() != Type.CONSTANT) {
				sweepingParameters.add(testParameter);
			}
		}

		// check any sweeping parameter exits.
		if (sweepingParameters.size() == 0) {
			returnList.add(paramList);
			return returnList;
		}
		// prepare constant parameters list.
		List<TestParameter> constantParams = new Vector<TestParameter>();
		constantParams.addAll(paramList);
		constantParams.removeAll(sweepingParameters);
		// create a temporary return list.
		List<List<TestParameter>> lst = new Vector<List<TestParameter>>();
		for (TestParameter sweepingParam : sweepingParameters) {
			List<TestParameter> tempList = new Vector<TestParameter>();
			for (int i = 0; i < sweepingParam.getSweepRun(); i++) {
				try {
					// clone test parameter..
					TestParameter newParam = (TestParameter) sweepingParam
							.clone();
					newParam.setParameterValue(sweepingParam.getNextValue(i));
					tempList.add(newParam);
				} catch (CloneNotSupportedException e) {
					e.printStackTrace();
				}
			}
			lst.add(tempList);
		}
		// cartesian given parameter list.
		returnList = cartesian(lst);
		// add constant values to all lists.
		for (List<TestParameter> list : returnList) {
			list.addAll(constantParams);
		}
		return returnList;
	}

	/**
	 * Create a cartesian product of the given list of parameter lists.
	 * 
	 * @param list
	 *            list of cartesian parameter lists.
	 * @return cartesian of the given lists.
	 */
	private List<List<TestParameter>> cartesian(List<List<TestParameter>> list) {
		List<List<TestParameter>> result = new ArrayList<List<TestParameter>>();
		int numSets = list.size();
		TestParameter[] tmpResult = new TestParameter[numSets];
		cartesian(list, 0, tmpResult, result);
		return result;
	}

	/**
	 * Cretas the current cartesian index of the given list with given values.
	 * 
	 * @param list
	 *            list of params.
	 * @param n
	 *            cartesian index.
	 * @param tmpResult
	 *            temp results. If the cartesian operation is finished return
	 *            the result as list.
	 * @param result
	 *            return result.
	 */
	private void cartesian(List<List<TestParameter>> list, int n,
			TestParameter[] tmpResult, List<List<TestParameter>> result) {
		// if cartesian is finished return result.
		if (n == list.size()) {
			result.add(new ArrayList<TestParameter>(Arrays.asList(tmpResult)));
			return;
		}
		// foreach parameter cartesian list.
		for (TestParameter i : list.get(n)) {
			tmpResult[n] = i;
			cartesian(list, n + 1, tmpResult, result);
		}
	}
}
